import { expect } from 'chai';
import * as sinon from 'sinon';
import * as step from '@flow-step/step-toolkit';
import * as paramsModule from '../src/params';
import { Ros, getROSClient, getROSCertificate } from '../src/ros';
import Client from '@alicloud/ros20190910';

describe('ros', () => {
  let sandbox: sinon.SinonSandbox;

  beforeEach(() => {
    sandbox = sinon.createSandbox();
  });

  afterEach(() => {
    sandbox.restore();
    delete process.env.ROS_ACCESS_KEY_ID;
    delete process.env.ROS_ACCESS_KEY_SECRET;
    delete process.env.ROS_SECURITY_TOKEN;
    delete process.env.FLOW_JOB_TOKEN;
    delete process.env.SERVICE_CONNECTION_ID;
  });

  describe('Ros class', () => {
    let mockClient: any;
    let ros: Ros;

    beforeEach(() => {
      mockClient = {
        createStack: sandbox.stub(),
        listStacks: sandbox.stub(),
        updateStack: sandbox.stub(),
        validateTemplate: sandbox.stub()
      };
      ros = new Ros(mockClient);
    });

    it('should call client.createStack with correct parameters', async () => {
      const mockRequest = { regionId: 'cn-hangzhou' };
      const mockResponse = { body: { stackId: 'stack-123' } };
      mockClient.createStack.resolves(mockResponse);

      const result = await ros.createStack(mockRequest as any);

      expect(result).to.equal(mockResponse);
      expect(mockClient.createStack.calledWith(mockRequest)).to.be.true;
    });

    it('should call client.listStacks with correct parameters', async () => {
      const mockRequest = { regionId: 'cn-hangzhou' };
      const mockResponse = { body: { stacks: [] } };
      mockClient.listStacks.resolves(mockResponse);

      const result = await ros.listStacks(mockRequest as any);

      expect(result).to.equal(mockResponse);
      expect(mockClient.listStacks.calledWith(mockRequest)).to.be.true;
    });

    it('should call client.updateStack with correct parameters', async () => {
      const mockRequest = { stackId: 'stack-123' };
      const mockResponse = { body: { stackId: 'stack-123' } };
      mockClient.updateStack.resolves(mockResponse);

      const result = await ros.updateStack(mockRequest as any);

      expect(result).to.equal(mockResponse);
      expect(mockClient.updateStack.calledWith(mockRequest)).to.be.true;
    });

    it('should call client.validateTemplate with correct parameters', async () => {
      const mockRequest = { templateBody: '{}' };
      const mockResponse = { body: { valid: true } };
      mockClient.validateTemplate.resolves(mockResponse);

      const result = await ros.validateTemplate(mockRequest as any);

      expect(result).to.equal(mockResponse);
      expect(mockClient.validateTemplate.calledWith(mockRequest)).to.be.true;
    });
  });

  describe('getROSClient', () => {
    let stepInfoStub: sinon.SinonStub;
    let stepExportEnvVarStub: sinon.SinonStub;
    let stepAddOutputStub: sinon.SinonStub;
    let getParamsStub: sinon.SinonStub;
    let getROSCertificateStub: sinon.SinonStub;

    beforeEach(() => {
      stepInfoStub = sandbox.stub(step, 'info');
      stepExportEnvVarStub = sandbox.stub(step, 'exportEnvVar');
      stepAddOutputStub = sandbox.stub(step, 'addOutput');
      getParamsStub = sandbox.stub(paramsModule, 'getParams');
    });

    it('should use existing environment variables when available', async () => {
      process.env.ROS_ACCESS_KEY_ID = 'existing-key';
      process.env.ROS_ACCESS_KEY_SECRET = 'existing-secret';
      process.env.ROS_SECURITY_TOKEN = 'existing-token';
      getParamsStub.returns({ ROS_DEPLOYMENT_LOCATION: 'cn' });

      const client = await getROSClient();

      expect(stepInfoStub.called).to.be.false;
      expect(stepExportEnvVarStub.called).to.be.false;
      expect(client).to.be.instanceOf(Client);
    });

    it('should fetch certificate when environment variables are missing', async () => {
      const originalEnv = process.env;
      process.env = {
        FLOW_JOB_TOKEN: 'test-token',
        SERVICE_CONNECTION_ID: 'test-connection'
      };
      const mockCertificate = {
        accessKeyId: 'new-key',
        accessKeySecret: 'new-secret',
        securityToken: 'new-token',
        accountId: '123456789'
      };
      sandbox.stub(step.certificate, 'getRamStsCertificate').resolves(mockCertificate);
      getParamsStub.returns({ ROS_DEPLOYMENT_LOCATION: 'cn' });

      const client = await getROSClient();

      expect(stepInfoStub.calledWith('Getting ROS Certificate')).to.be.true;
      expect(stepExportEnvVarStub.calledWith('ROS_ACCESS_KEY_ID', 'new-key')).to.be.true;
      expect(stepExportEnvVarStub.calledWith('ROS_ACCESS_KEY_SECRET', 'new-secret')).to.be.true;
      expect(stepExportEnvVarStub.calledWith('ROS_SECURITY_TOKEN', 'new-token')).to.be.true;
      expect(client).to.be.instanceOf(Client);
      
      process.env = originalEnv;
    });

    it('should use intl endpoint for international deployment', async () => {
      process.env.ROS_ACCESS_KEY_ID = 'test-key';
      process.env.ROS_ACCESS_KEY_SECRET = 'test-secret';
      process.env.ROS_SECURITY_TOKEN = 'test-token';
      getParamsStub.returns({ ROS_DEPLOYMENT_LOCATION: 'intl' });

      const client = await getROSClient();

      expect(client).to.be.instanceOf(Client);
      // Note: We can't directly test the endpoint, but we verify the client is created
    });

    it('should use cn endpoint for domestic deployment', async () => {
      process.env.ROS_ACCESS_KEY_ID = 'test-key';
      process.env.ROS_ACCESS_KEY_SECRET = 'test-secret';
      process.env.ROS_SECURITY_TOKEN = 'test-token';
      getParamsStub.returns({ ROS_DEPLOYMENT_LOCATION: 'cn' });

      const client = await getROSClient();

      expect(client).to.be.instanceOf(Client);
    });

    it('should add output for environment variables', async () => {
      process.env.ROS_ACCESS_KEY_ID = 'test-key';
      process.env.ROS_ACCESS_KEY_SECRET = 'test-secret';
      process.env.ROS_SECURITY_TOKEN = 'test-token';
      getParamsStub.returns({ ROS_DEPLOYMENT_LOCATION: 'cn' });

      await getROSClient();

      expect(stepAddOutputStub.calledWith('ROS_ACCESS_KEY_ID=test-key')).to.be.true;
      expect(stepAddOutputStub.calledWith('ROS_ACCESS_KEY_SECRET=test-secret')).to.be.true;
      expect(stepAddOutputStub.calledWith('ROS_SECURITY_TOKEN=test-token')).to.be.true;
    });
  });

  describe('getROSCertificate', () => {
    let stepDebugStub: sinon.SinonStub;

    beforeEach(() => {
      stepDebugStub = sandbox.stub(step, 'debug');
    });

    it('should throw error when FLOW_JOB_TOKEN is missing', async () => {
      delete process.env.FLOW_JOB_TOKEN;

      try {
        await getROSCertificate();
        expect.fail('Should have thrown an error');
      } catch (error) {
        expect(error).to.be.instanceOf(Error);
        expect((error as Error).message).to.contain('FLOW_JOB_TOKEN');
      }
    });

    it('should throw error when SERVICE_CONNECTION_ID is missing', async () => {
      process.env.FLOW_JOB_TOKEN = 'test-token';
      delete process.env.SERVICE_CONNECTION_ID;

      try {
        await getROSCertificate();
        expect.fail('Should have thrown an error');
      } catch (error) {
        expect(error).to.be.instanceOf(Error);
      }
    });

    it('should throw error when certificate is undefined', async () => {
      process.env.FLOW_JOB_TOKEN = 'test-token';
      process.env.SERVICE_CONNECTION_ID = 'test-connection';
      sandbox.stub(step.certificate, 'getRamStsCertificate').resolves(undefined);

      try {
        await getROSCertificate();
        expect.fail('Should have thrown an error');
      } catch (error) {
        expect(error).to.be.instanceOf(Error);
        expect((error as Error).message).to.contain('Get RamStsCertificate but error');
      }
    });

    it('should throw error when accessKeyId is empty', async () => {
      process.env.FLOW_JOB_TOKEN = 'test-token';
      process.env.SERVICE_CONNECTION_ID = 'test-connection';
      sandbox.stub(step.certificate, 'getRamStsCertificate').resolves({
        accessKeyId: '',
        accessKeySecret: 'secret',
        securityToken: 'token',
        accountId: '123456789'
      });

      try {
        await getROSCertificate();
        expect.fail('Should have thrown an error');
      } catch (error) {
        expect(error).to.be.instanceOf(Error);
        expect((error as Error).message).to.contain('Get RamStsCertificate but error');
      }
    });

    it('should return valid certificate', async () => {
      process.env.FLOW_JOB_TOKEN = 'test-token';
      process.env.SERVICE_CONNECTION_ID = 'test-connection';
      const mockCertificate = {
        accessKeyId: 'valid-key',
        accessKeySecret: 'valid-secret',
        securityToken: 'valid-token',
        accountId: '123456789'
      };
      sandbox.stub(step.certificate, 'getRamStsCertificate').resolves(mockCertificate);

      const result = await getROSCertificate();

      expect(result).to.deep.equal(mockCertificate);
      expect(stepDebugStub.called).to.be.true;
    });

    it('should handle certificate fetch errors', async () => {
      process.env.FLOW_JOB_TOKEN = 'test-token';
      process.env.SERVICE_CONNECTION_ID = 'test-connection';
      sandbox.stub(step.certificate, 'getRamStsCertificate').rejects(new Error('Network error'));

      try {
        await getROSCertificate();
        expect.fail('Should have thrown an error');
      } catch (error) {
        expect(error).to.be.instanceOf(Error);
        expect((error as Error).message).to.equal('Network error');
      }
    });
  });
});